﻿// Copyright (c) Inossem. All rights reserved.
// Licensed under the LGPL-3.0 license. See LICENSE file in the project root for full license information.
using DWF.Studio.ScriptEngine.Event;
using DWF.Studio.ScriptEngine.IPC.Msg;
using DWF.Studio.ScriptEngine.IPC.NamedPipe;
using DWF.Studio.Share.Log;
using DWF.Studio.Share.Serialization;
using DWF.Studio.Share.Utils;
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace DWF.Studio.ScriptEngine.Custom
{
    /// <summary>
    /// 执行上下文
    /// </summary>
    internal class ExecutionContext : IDisposable
    {
        private static ExecutionContext _instance = null;

        public StartupProperties StartupProps { get; private set; }
        /// <summary>
        /// 初始化执行上下文
        /// </summary>
        /// <param name="pipeName"></param>
        public static void Initialize(string pipeName, string mainProcessId, string[] resolutionPaths, Dictionary<string, string> projectProperties)
        {
            _instance = new ExecutionContext(pipeName, mainProcessId, resolutionPaths, projectProperties);
        }

        /// <summary>
        /// 执行上下文实例
        /// </summary>
        public static ExecutionContext Instance
        {
            get
            {
                return _instance;
            }
        }

        private string[] _assemblyResolutionPaths = null;

        private Dictionary<string, string> _projectProperties = null;

        /// <summary>
        /// 通讯管道
        /// </summary>
        private NamedPipeClient _namedPipeClient = null;

        /// <summary>
        /// 执行引擎栈
        /// </summary>
        private ConcurrentStack<ScriptEngineBase> _executeStack = null;

        private AutoThreadBlock _autoThreadBlock = null;
        /// <summary>
        /// 同步上下文
        /// </summary>
        public QueueSynchronizationContext SyncContext
        {
            private set;
            get;
        }

        public EDebugStatus DebugStatus
        {
            get;
            private set;
        }

        private ExecutionContext(string pipeName, string mainProcessId, string[] resolutionPaths, Dictionary<string, string> projectProperties)
        {
            init(pipeName, mainProcessId, resolutionPaths, projectProperties);
        }

        private void init(string pipeName, string mainProcessId, string[] resolutionPaths, Dictionary<string, string> projectProperties)
        {
            _projectProperties = projectProperties;
            //启动进程传递的记录日志所需参数 Oliver add 2021-06-25
            StartupProps = new StartupProperties(projectProperties);

            _assemblyResolutionPaths = resolutionPaths;
            AppDomain.CurrentDomain.AssemblyResolve += assemblyResolve;

            _autoThreadBlock = new AutoThreadBlock();
            SyncContext = new QueueSynchronizationContext();
            _executeStack = new ConcurrentStack<ScriptEngineBase>();
            _namedPipeClient = new NamedPipeClient(pipeName);

            _namedPipeClient.ReceiveMessage = (msg) =>
            {
                if (msg.MsgType == EMsgType.DebugTrack)
                {
                    var datum = msg.GetDatum<string>();
                    if (datum == "Msg_Debug_Resume")
                    {
                        if (_executeStack.TryPeek(out ScriptEngineBase engine) && engine.Id == msg.EngineId)
                        {
                            DebugStatus = EDebugStatus.Debug;
                            engine.DebugResume();
                        }
                    }
                    else if (datum == "Msg_Debug_StepInto")
                    {
                        if (_executeStack.TryPeek(out ScriptEngineBase engine) && engine.Id == msg.EngineId)
                        {
                            DebugStatus = EDebugStatus.DebugByStep;
                            engine.DebugStepInto();
                        }
                    }
                    else if (datum == "Msg_Debug_StepOut")
                    {
                        if (_executeStack.TryPeek(out ScriptEngineBase engine) && engine.Id == msg.EngineId)
                        {
                            DebugStatus = EDebugStatus.DebugByStep;
                            engine.DebugStepOut();
                        }
                    }
                    else if (datum == "Msg_Debug_StepOver")
                    {
                        if (_executeStack.TryPeek(out ScriptEngineBase engine) && engine.Id == msg.EngineId)
                        {
                            DebugStatus = EDebugStatus.DebugByStep;
                            engine.DebugStepOver();
                        }
                    }
                    else if (datum == "Msg_Debug_CallFrame")
                    {
                        if (_executeStack.TryPeek(out ScriptEngineBase engine) && engine.Id == msg.EngineId)
                        {
                            engine.CallFrameInfo();
                        }
                    }
                }
                else if (msg.MsgType == EMsgType.ExcuteScript)
                {
                    var datum = msg.GetDatum<string[]>();
                    var projectDirectory = datum[0];
                    var scriptPath = datum[1];
                    Dictionary<string, object> param = null;
                    if (datum.Length == 3 && !String.IsNullOrWhiteSpace(datum[2]))
                    {
                        param = JsonHelper.DeserializeFloatAsDecimal<Dictionary<string, object>>(datum[2]);
                    }

                    if (_executeStack.TryPeek(out ScriptEngineBase engine) && engine.Id == msg.EngineId)
                    {
                        if (param == null)
                        {
                            engine.ExecuteScript(projectDirectory, scriptPath);
                        }
                        else
                        {
                            engine.ExecuteScript(projectDirectory, scriptPath, param);
                        }
                    }
                }
                else if (msg.MsgType == EMsgType.DebugScript)
                {
                    var datum = msg.GetDatum<string[]>();
                    var projectDirectory = datum[0];
                    var scriptPath = datum[1];
                    Dictionary<string, object> param = null;
                    if (datum.Length == 3 && !String.IsNullOrWhiteSpace(datum[2]))
                    {
                        param = JsonHelper.DeserializeFloatAsDecimal<Dictionary<string, object>>(datum[2]);
                    }

                    if (_executeStack.TryPeek(out ScriptEngineBase engine) && engine.Id == msg.EngineId)
                    {
                        if (param == null)
                        {
                            engine.DebugScript(projectDirectory, scriptPath);
                        }
                        else
                        {
                            engine.DebugScript(projectDirectory, scriptPath, param);
                        }
                    }
                }
                else if (msg.MsgType == EMsgType.DebugStatus)
                {
                    if (_executeStack.TryPeek(out ScriptEngineBase engine) && engine.Id == msg.EngineId)
                    {
                        var datum = msg.GetDatum<bool>();
                        if (datum)
                        {
                            DebugStatus = EDebugStatus.DebugByStep;
                        }
                        else
                        {
                            DebugStatus = EDebugStatus.Debug;
                        }
                    }
                }
                else if (msg.MsgType == EMsgType.RefreshBreakpoints)
                {
                    var datum = msg.GetDatum<string>();
                    if (datum != null)
                    {
                        foreach (var engine in _executeStack)
                        {
                            var localEngine = engine as ILocalScriptEngine;
                            localEngine?.RefreshBreakpoints(datum);
                        }
                    }
                }
                else if (msg.MsgType == EMsgType.CancelScript)
                {
                    if (_executeStack.TryPeek(out ScriptEngineBase engine) && engine.Id == msg.EngineId)
                    {
                        engine.Cancel();
                    }
                }
                else if (msg.MsgType == EMsgType.Close)
                {
                    _namedPipeClient.Disconnnect();
                    SyncContext.Complete();
                    MessageLoggerHelper.Log<ExecuteEndEventArgs>(new ExecuteEndEventArgs(false), EMsgType.ExcuteEnd, StartupProps);
                }
                else if (msg.MsgType == EMsgType.LoadedSubEngine)
                {
                    if (_executeStack.TryPeek(out ScriptEngineBase engine) && engine.Id == msg.EngineId)
                    {
                        _autoThreadBlock.Continue();
                    }
                }
                else if (msg.MsgType == EMsgType.UnloadSubEngineEcho)  //Oliver add 2021-06-21 子引擎卸载后，将子引擎id回传，并记录日志 
                {
                    MessageLoggerHelper.Log<UnLoadSubEngineEventArgs>(new UnLoadSubEngineEventArgs() { SubEngineId = msg.EngineId }, EMsgType.UnloadSubEngineEcho, StartupProps);  //直接记录日志 Oliver add 2021-07-02
                }
            };

            int processId;
            if (Int32.TryParse(mainProcessId, out processId))
            {
                var mainProcess = Process.GetProcessById(processId);
                if (mainProcess != null)
                {
                    mainProcess.EnableRaisingEvents = true;
                    mainProcess.Exited += (sender, e) =>
                    {
                        SyncContext.Complete();
                    };
                }
            }
        }

        /// <summary>
        /// 启动上下文
        /// </summary>
        public void Run()
        {
            _namedPipeClient.Connnect(5000);//连接studio
            if (_executeStack.TryPeek(out ScriptEngineBase engine))
            {
                SendMsg<string>(engine.Id, EMsgType.Echo, string.Empty, EEngineType.Ignore);//呼叫
                SyncContext.Run();
            }
        }

        public string GetProjectProperty(string key)
        {
            //string result = null;
            //if (_projectProperties != null && _projectProperties.ContainsKey(key))
            //{
            //    result = _projectProperties[key];
            //}

            return getProjectProperty(key, _projectProperties);
        }
        private string getProjectProperty<T>(string key, T dict) where T : IDictionary
        {
            string result = string.Empty;
            if (dict != null && dict.Contains(key))
            {
                result = dict[key].ToString();
            }
            return result;
        }

        /// <summary>
        /// 压入引擎
        /// </summary>
        /// <param name="scriptEngine"></param>
        public void Push(ScriptEngineBase scriptEngine)
        {
            _executeStack.Push(scriptEngine);
        }

        /// <summary>
        /// 弹出引擎
        /// </summary>
        /// <param name="scriptEngine"></param>
        public void Pop(ScriptEngineBase scriptEngine)
        {
            if (_executeStack.TryPop(out ScriptEngineBase head) && head != scriptEngine)
            {
                throw new EngineException();
            }

            head?.Dispose();
        }

        public void WaitSubEngineProxyLoaded(string projectDirectory, string scriptPath, string subEngineId, EEngineType subEngineType, string parentEngineId, EEngineType parentEngineType = EEngineType.WorkflowScriptEngine)
        {
            SendMsg<LoadSubEngineEventArgs>(new LoadSubEngineEventArgs() { ScriptPath = scriptPath, SubEngineId = subEngineId, SubEngineType = subEngineType }, EMsgType.LoadSubEngine, parentEngineId, parentEngineType);
            _autoThreadBlock.Block();
        }

        /// <summary>
        /// 发送消息
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="datum"></param>
        /// <param name="msgType"></param>
        /// <param name="engineType"></param>
        public void SendMsg<T>(T datum, EMsgType msgType, string engineId, EEngineType engineType)
        {
            //如果需要就记录日志，并判断是否是拦截消息
            if (msgType != EMsgType.ExcuteEnd)  //执行结束消息日志不在这里记录，发送消息后后续还会有尾部动作
            {
                bool isFilterMsg = MessageLoggerHelper.Log<T>(datum, msgType, StartupProps);
                if (isFilterMsg) return;
            }

            UpwardMsg upwardMsg = new UpwardMsg();
            upwardMsg.EngineId = engineId;
            upwardMsg.EngineType = engineType;
            upwardMsg.MsgType = msgType;
            upwardMsg.SetDatum(datum);
            _namedPipeClient.Send(upwardMsg);
        }

        private Assembly assemblyResolve(object sender, ResolveEventArgs args)
        {
            Assembly[] loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies();
            foreach (Assembly asm in loadedAssemblies)
            {
                if (asm.FullName.Equals(args.Name))
                    return asm;
            }

            AssemblyName asmInfo = new AssemblyName(args.Name);
            if (_assemblyResolutionPaths != null)
            {
                for (int i = 0; i < _assemblyResolutionPaths.Length; i++)
                {
                    string asmFilePath = Path.Combine(_assemblyResolutionPaths[i], asmInfo.Name + ".dll");
                    if (File.Exists(asmFilePath))
                    {
                        return Assembly.LoadFrom(asmFilePath);
                    }
                    else
                    {
                        asmFilePath = Path.Combine(_assemblyResolutionPaths[i], asmInfo.Name + ".exe");
                        if (File.Exists(asmFilePath))
                            return Assembly.LoadFrom(asmFilePath);
                    }
                }
            }

            return null;
        }

        #region IDisposable Support
        private bool disposedValue = false; // 要检测冗余调用

        protected virtual void Dispose(bool disposing)
        {
            if (!disposedValue)
            {
                if (disposing)
                {
                    // TODO: 释放托管状态(托管对象)。
                    SyncContext.Dispose();
                    _namedPipeClient.Disconnnect();
                    StartupProps.Dispose();  //Oliver add 2021-06-25
                }

                // TODO: 释放未托管的资源(未托管的对象)并在以下内容中替代终结器。
                // TODO: 将大型字段设置为 null。

                disposedValue = true;
            }
        }

        // TODO: 仅当以上 Dispose(bool disposing) 拥有用于释放未托管资源的代码时才替代终结器。
        // ~ExcutionContext()
        // {
        //   // 请勿更改此代码。将清理代码放入以上 Dispose(bool disposing) 中。
        //   Dispose(false);
        // }

        // 添加此代码以正确实现可处置模式。
        public void Dispose()
        {
            // 请勿更改此代码。将清理代码放入以上 Dispose(bool disposing) 中。
            Dispose(true);
            // TODO: 如果在以上内容中替代了终结器，则取消注释以下行。
            // GC.SuppressFinalize(this);
        }
        #endregion
    }
}
